package com.demo.ehcahe.configuration;

import org.ehcache.config.CacheConfiguration;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.impl.serialization.PlainJavaSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Callable;

/**
 * <b>
 * ehcache集成可以直接通过配置文件集成。并不一定需要此自定义配置。
 * 由于此版本ehcahe为3.6.2,而springboot还是用的3.0之前的版本，因此自定义实现ehcache的缓存。
 * 官网地址：http://www.ehcache.org/documentation/3.6/getting-started.html
 * </b>
 * <p>
 * 一、ehcache是分层缓存的
 * 1.heap 堆内缓存，也就是存储在JVM内存中;
 * 2.off heap 堆外缓存，非JVM单独非配的一部分内存。因此，设置的参数要与jvm启动参数相协调。
 * 3.disk 磁盘缓存，也就是将缓存内容写入到磁盘文件中。
 * 4.cluster 集群缓存，它是需要已cluster服务器来处同步存储这些数据。
 * 注意：
 * 1.heap是必须配置的；
 * 2.disk tier不能与cluster tier共存，因为，它们回产生不一致；
 * 3.缓存tier是金字塔的配置，也就是 heap > off heap > disk > cluster，并且配置的大小是一次递增。
 * 4.除了heap tier之外，其它层都必须设置key和value的序列化器和反序列化器。另外基础数据类型，已经实现其对相应的序列化/反序列化器。
 * <p>
 * 二、ResourcePoolsBuilder是每个cache独立的，它仅仅只是配置信息。如：如果配置heap=20M,那么每个cache都有20M.
 * <p>
 * 三、过期时间设置策略
 * 如:withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofHours(24))
 * 1.no expiry 永不过期
 * 2.time-to-live 从缓存开始计算，所存储的时间
 * 3.time-to-idle 最后一次使用后计算，所存储的时间
 * <p>
 * 更多信息参考个人博客：http://blog.51cto.com/881206524/2320745
 * </p>
 */
@Configuration
public class CacheConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(CacheConfig.class);
    private static final String CACHE_NAME_1 = "ehCache1";
    private static final String CACHE_NAME_2 = "ehCache2";

    @Bean
    public CacheManager cacheManager() {
        ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder().heap(20, MemoryUnit.MB).offheap(30, MemoryUnit.MB);
        CacheConfiguration<String, Object> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Object.class, resourcePoolsBuilder)
                .withValueSerializer(new PlainJavaSerializer<>(this.getClass().getClassLoader()))
                .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofHours(24))).build();
        org.ehcache.CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                .withDefaultSizeOfMaxObjectSize(500, MemoryUnit.KB)
                .withDefaultSizeOfMaxObjectGraph(2000)
                .withCache(CACHE_NAME_1, cacheConfiguration)
                .withCache(CACHE_NAME_2, cacheConfiguration)
                .build(true);
        return new CacheManager() {
            @Override
            public Cache getCache(String name) {
                org.ehcache.Cache<String, Object> heapEhcache = cacheManager.getCache(name, String.class, Object.class);
                return new Cache() {
                    @Override
                    public String getName() {
                        return name;
                    }

                    @Override
                    public Object getNativeCache() {
                        return heapEhcache;
                    }

                    @Override
                    public ValueWrapper get(Object key) {
                        Object value = heapEhcache.get(String.valueOf(key));
                        // value=null时，说明cache中没有缓存该值，则直接返回null,这样就会获取业务代码的返回值并缓存；
                        // value != null时，说明cache中命中该key,则需要用ValueWrapper包裹并返回；
                        return null == value ? null : () -> value;
                    }

                    @Override
                    public <T> T get(Object key, Class<T> type) {
                        Object value = heapEhcache.get(String.valueOf(key));
                        if (value != null && type != null && !type.isInstance(value)) {
                            throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
                        }
                        return (T) value;
                    }

                    @Override
                    public <T> T get(Object key, Callable<T> valueLoader) {
                        try {
                            return valueLoader.call();
                        } catch (Exception e) {
                            LOGGER.error("ehcache valueLoader.call occur error", e);
                        }
                        return null;
                    }

                    @Override
                    public void put(Object key, Object value) {
                        heapEhcache.put(String.valueOf(key), value);
                    }

                    @Override
                    public ValueWrapper putIfAbsent(Object key, Object value) {
                        Object putIfAbsent = heapEhcache.putIfAbsent(String.valueOf(key), value);
                        return putIfAbsent == null ? null : () -> putIfAbsent;
                    }

                    @Override
                    public void evict(Object key) {
                        heapEhcache.remove(String.valueOf(key));
                    }

                    @Override
                    public void clear() {
                        heapEhcache.clear();
                    }
                };
            }

            @Override
            public Collection<String> getCacheNames() {
                String[] cacheNames = {CACHE_NAME_1, CACHE_NAME_2};
                return Arrays.asList(cacheNames);
            }
        };
    }
}
